unit DIrr.AabBox3D;

{$I DIrr.inc}

interface

uses
  Math, DIrr.Math, DIrr.Vector3D, DIrr.Line3D, DIrr.Plane3D;

type
  TBoxEdgesF = array[0..7] of TVector3DF;

  //! Axis aligned bounding box in 3d dimensional space.
  (* Has some useful methods used with occlusion culling or clipping.*)
  PAabBox3DF = ^TAabbox3DF;
  TAabbox3DF = record
    MinEdge: TVector3DF;
    MaxEdge: TVector3DF;
  public
    constructor Create(const Min, Max: TVector3DF); overload;
    constructor Create(const Init: TVector3DF); overload;
  public
    class operator Equal(Value1, Value2: TAabbox3DF): Boolean;
    class operator NotEqual(Value1, Value2: TAabbox3DF): Boolean;

    //Set default value
    procedure Reset; overload;
		//! Resets the bounding box to a one-point box.
		(* \param x X coord of the point.
		\param y Y coord of the point.
		\param z Z coord of the point. *)
    procedure Reset(x, y, z: Single); overload;
		//! Resets the bounding box.
		(* \param initValue New box to set this one to. *)
    procedure Reset(const InitValue: TAabbox3DF); overload;
		//! Resets the bounding box to a one-point box.
		(* \param initValue New point. *)
    procedure Reset(const InitValue: TVector3DF); overload;
		//! Adds a point to the bounding box
		(* The box grows bigger, if point is outside of the box.
		\param x X coordinate of the point to add to this box.
		\param y Y coordinate of the point to add to this box.
		\param z Z coordinate of the point to add to this box. *)
    procedure AddInternalPoint(x, y, z: Single); overload;
		//! Adds a point to the bounding box
		(* The box grows bigger, if point was outside of the box.
		\param p: Point to add into the box. *)
    procedure AddInternalPoint(const p: TVector3DF); overload;
		//! Adds another bounding box
		(* The box grows bigger, if the new box was outside of the box.
		\param b: Other bounding box to add into this box. *)
    procedure AddInternalBox(const b: TAabbox3DF);
		//! Get center of the bounding box
		(* \return Center of the bounding box. *)
    function GetCenter: TVector3DF;
		//! Get extent of the box (maximal distance of two points in the box)
		(* \return Extent of the bounding box. *)
    function GetExtent: TVector3DF;
		//! Check if the box is empty.
		(* This means that there is no space between the min and max edge.
		\return True if box is empty, else false. *)
    function IsEmpty: Boolean;
    //! Get the volume enclosed by the box in cubed units
    function GetVolume: Single;
    //! Get the surface area of the box in squared units
    function GetArea: Single;
		//! Stores all 8 edges of the box into an array
		(* \param edges: Pointer to array of 8 edges. *)
    procedure GetEdges(var Edges: TBoxEdgesF);
		//! Repairs the box.
		(* Necessary if for example MinEdge and MaxEdge are swapped. *)
    procedure Repair;
		//! Calculates a new interpolated bounding box.
		(* d=0 returns other, d=1 returns this, all other values blend between
		the two boxes.
		\param other Other box to interpolate between
		\param d Value between 0.0f and 1.0f.
		\return Interpolated box. *)
    function GetInterpolated(const Other: TAabbox3DF; d: Double): TAabbox3DF;
		//! Determines if a point is within this box.
		(* Border is included (IS part of the box)!
		\param p: Point to check.
		\return True if the point is within the box and false if not *)
    function IsPointInside(const p: TVector3DF): Boolean;
		//! Determines if a point is within this box and not its borders.
		(* Border is excluded (NOT part of the box)!
		\param p: Point to check.
		\return True if the point is within the box and false if not. *)
    function IsPointTotalInside(const p: TVector3DF): Boolean;
		//! Check if this box is completely inside the 'other' box.
		(* \param other: Other box to check against.
		\return True if this box is completly inside the other box,
		otherwise false. *)
    function IsFullInside(const Other: TAabbox3DF): Boolean;
		//! Determines if the axis-aligned box intersects with another axis-aligned box.
		(* \param other: Other box to check a intersection with.
		\return True if there is an intersection with the other box,
		otherwise false. *)
    function IntersectsWithBox(const Other: TAabbox3DF): Boolean;
		//! Tests if the box intersects with a line
		(* \param line: Line to test intersection with.
		\return True if there is an intersection , else false. *)
    function IntersectsWithLine(const Line: TLine3DF): Boolean; overload;
		//! Tests if the box intersects with a line
		(* \param linemiddle Center of the line.
		\param linevect Vector of the line.
		\param halflength Half length of the line.
		\return True if there is an intersection, else false. *)
    function IntersectsWithLine(const LineMiddle, LineVect: TVector3DF;
      HalfLength: Single): Boolean; overload;
		//! Classifies a relation with a plane.
		(* \param plane Plane to classify relation to.
		\return Returns ISREL3D_FRONT if the box is in front of the plane,
		ISREL3D_BACK if the box is behind the plane, and
		ISREL3D_CLIPPED if it is on both sides of the plane. *)
    function ClassifyPlaneRelation(const Plane: TPlane3DF): TIntersectionRelation3D;
  end;

  function AabBox3DF(MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Single): TAabbox3DF; {$IFDEF INLINE} inline; {$ENDIF}

implementation


function AabBox3DF(MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Single): TAabbox3DF;
begin
  Result.MinEdge.X := MinX;
  Result.MinEdge.Y := MinY;
  Result.MinEdge.Z := MinZ;
  Result.MaxEdge.X := MaxX;
  Result.MaxEdge.Y := MaxY;
  Result.MaxEdge.Z := MaxZ;
end;

{ TAabbox3DF }

procedure TAabbox3DF.AddInternalBox(const b: TAabbox3DF);
begin
  AddInternalPoint(b.MaxEdge);
  AddInternalPoint(b.MinEdge);
end;

procedure TAabbox3DF.AddInternalPoint(x, y, z: Single);
begin
  if x > MaxEdge.X then MaxEdge.X := x;
  if y > MaxEdge.Y then MaxEdge.Y := y;
  if z > MaxEdge.z then MaxEdge.Z := z;

  if x < MinEdge.X then MinEdge.X := x;
  if y < MinEdge.Y then MinEdge.Y := y;
  if z < MinEdge.z then MinEdge.Z := z;
end;

procedure TAabbox3DF.AddInternalPoint(const p: TVector3DF);
begin
  AddInternalPoint(p.X, p.Y, p.Z);
end;

function TAabbox3DF.ClassifyPlaneRelation(
  const Plane: TPlane3DF): TIntersectionRelation3D;
var
  NearPoint, FarPoint: TVector3DF;
begin
  if Plane.Normal.X > 0 then
  begin
    NearPoint.X := MinEdge.X;
    FarPoint.X := MaxEdge.X;
  end;

  if Plane.Normal.Y > 0 then
  begin
    NearPoint.Y := MinEdge.Y;
    FarPoint.Y := MaxEdge.Y;
  end;

  if Plane.Normal.Z > 0 then
  begin
    NearPoint.Z := MinEdge.Z;
    FarPoint.Z := MaxEdge.Z;
  end;

  if Plane.Normal.DotProduct(NearPoint) + Plane.Distance > 0 then
  begin
    Result := ir3DFront;
    Exit;
  end;

  if Plane.Normal.DotProduct(FarPoint) + Plane.Distance > 0 then
  begin
    Result := ir3DClipped;
    Exit;
  end;

  Result := ir3DBack;
end;

constructor TAabbox3DF.Create(const Min, Max: TVector3DF);
begin
  MinEdge := Min;
  MaxEdge := Max;
end;

constructor TAabbox3DF.Create(const Init: TVector3DF);
begin
  MinEdge := Init;
  MaxEdge := Init;
end;

class operator TAabbox3DF.Equal(Value1, Value2: TAabbox3DF): Boolean;
begin
  Result := (Value1.MinEdge = Value2.MinEdge) and (Value1.MaxEdge = Value2.MaxEdge);
end;

function TAabbox3DF.GetArea: Single;
var
  e: TVector3DF;
begin
  e := GetExtent;
  Result := 2 * (e.X * e.Y + e.X * e.Z + e.Y * e.Z);
end;

function TAabbox3DF.GetCenter: TVector3DF;
begin
  Result := (MinEdge + MaxEdge) / 2;
end;

procedure TAabbox3DF.GetEdges(var Edges: TBoxEdgesF);
  procedure SetVector3D(var Vector: TVector3DF; X, Y, Z: Single); inline;
  begin

  end;
var
  Middle, Diag: TVector3DF;
begin
  Middle := GetCenter;
  Diag := Middle - MaxEdge;

  (*
  Edges are stored in this way:
  Hey, am I an ascii artist, or what? :) niko.
               /3--------/7
              / |       / |
             /  |      /  |
            1---------5   |
            |  /2- - -|- -6
            | /       |  /
            |/        | /
            0---------4/
  *)

  SetVector3D(Edges[0], Middle.X + Diag.X, Middle.Y + Diag.Y,  Middle.Z + Diag.Z);
  SetVector3D(Edges[1], Middle.X + Diag.X, Middle.Y - Diag.Y,  Middle.Z + Diag.Z);
  SetVector3D(Edges[2], Middle.X + Diag.X, Middle.Y + Diag.Y,  Middle.Z - Diag.Z);
  SetVector3D(Edges[3], Middle.X + Diag.X, Middle.Y - Diag.Y,  Middle.Z - Diag.Z);
  SetVector3D(Edges[4], Middle.X - Diag.X, Middle.Y + Diag.Y,  Middle.Z + Diag.Z);
  SetVector3D(Edges[5], Middle.X - Diag.X, Middle.Y - Diag.Y,  Middle.Z + Diag.Z);
  SetVector3D(Edges[6], Middle.X - Diag.X, Middle.Y + Diag.Y,  Middle.Z - Diag.Z);
  SetVector3D(Edges[7], Middle.X - Diag.X, Middle.Y - Diag.Y,  Middle.Z - Diag.Z);
end;

function TAabbox3DF.GetExtent: TVector3DF;
begin
  Result := MaxEdge - MinEdge;
end;

function TAabbox3DF.GetInterpolated(const Other: TAabbox3DF;
  d: Double): TAabbox3DF;
var
  inv: Double;
begin
  inv := 1.0 - d;
  Result.MinEdge := Other.MinEdge * inv + MinEdge * d;
  Result.MaxEdge := Other.MaxEdge * inv + MaxEdge * d;
end;

function TAabbox3DF.GetVolume: Single;
var
  e: TVector3DF;
begin
  e := GetExtent;
  Result := e.X * e.Y * e.Z;
end;

function TAabbox3DF.IntersectsWithBox(const Other: TAabbox3DF): Boolean;
begin
	Result := (MinEdge.X <= Other.MaxEdge.X) and (MinEdge.Y <= Other.MaxEdge.Y) and
    (MinEdge.Z <= Other.MaxEdge.Z) and (MaxEdge.X >= Other.MinEdge.X) and
    (MaxEdge.Y >= Other.MinEdge.Y) and (MaxEdge.Z >= Other.MinEdge.Z);
end;

function TAabbox3DF.IntersectsWithLine(const LineMiddle,
  LineVect: TVector3DF; HalfLength: Single): Boolean;
var
  e, t: TVector3DF;
  r: Single;
begin
  e := GetExtent * 0.5;
  t := GetCenter - LineMiddle;

  if (Abs(t.X) > e.X + HalfLength * Abs(LineVect.X)) or
     (Abs(t.Y) > e.X + HalfLength * Abs(LineVect.Y)) or
     (Abs(t.Z) > e.X + HalfLength * Abs(LineVect.Z)) then
  begin
    Result := False;
    exit;
  end;

  r := e.Y * Abs(LineVect.Z) + e.Z * Abs(LineVect.Y);
  if (Abs(t.Y * LineVect.Z - t.Z * LineVect.Y) > r) then
  begin
    Result := False;
    Exit;
  end;

  r := e.X * Abs(LineVect.Z) + e.Z * Abs(LineVect.X);
  if (Abs(t.Z*LineVect.X - t.X*LineVect.Z) > r) then
  begin
    Result:= False;
    Exit;
  end;

  r := e.X * Abs(LineVect.Y) + e.Y * Abs(LineVect.X);
  if (Abs(t.X*LineVect.Y - t.Y*LineVect.X) > r) then
  begin
    Result:= False;
    Exit;
  end;

  Result := True;
end;

function TAabbox3DF.IntersectsWithLine(const Line: TLine3DF): Boolean;
var
  v: TVector3DF;
begin
  v := Line.GetVector;
  v.Normalize;

  Result := IntersectsWithLine(Line.GetMiddle, v, Line.GetLength * 0.5);
end;

function TAabbox3DF.IsEmpty: Boolean;
begin
  Result := (MinEdge = MaxEdge);
end;

function TAabbox3DF.IsFullInside(const Other: TAabbox3DF): Boolean;
begin
  Result := (MinEdge.X >= Other.MinEdge.X) and (MinEdge.Y >= Other.MinEdge.Y) and
    (MinEdge.Z >= Other.MinEdge.Z) and (MaxEdge.X <= Other.MaxEdge.X) and
    (MaxEdge.Y <= Other.MaxEdge.Y) and (MaxEdge.Z <= Other.MaxEdge.Z);
end;

function TAabbox3DF.IsPointInside(const p: TVector3DF): Boolean;
begin
  Result := (p.X >= MinEdge.X) and (p.X <= MaxEdge.X) and
    (p.Y >= MinEdge.Y) and (p.Y <= MaxEdge.Y) and
    (p.Z >= MinEdge.Z) and (p.Z <= MaxEdge.Z);
end;

function TAabbox3DF.IsPointTotalInside(const p: TVector3DF): Boolean;
begin
  Result := (p.X > MinEdge.X) and (p.X < MaxEdge.X) and
    (p.Y > MinEdge.Y) and (p.Y < MaxEdge.Y) and
    (p.Z > MinEdge.Z) and (p.Z < MaxEdge.Z);
end;

class operator TAabbox3DF.NotEqual(Value1, Value2: TAabbox3DF): Boolean;
begin
  Result := not ((Value1.MinEdge = Value2.MinEdge) and (Value1.MaxEdge = Value2.MaxEdge));
end;

procedure TAabbox3DF.Repair;
  procedure SwapValue(var A, B: Single); inline;
  var
    v: Single;
  begin
    v := A;
    A := B;
    B := v;
  end;

begin
  if MinEdge.X > MaxEdge.X then
  begin
    SwapValue(MinEdge.X, MaxEdge.X);
  end;
  if MinEdge.Y > MaxEdge.Y then
  begin
    SwapValue(MinEdge.Y, MaxEdge.Y);
  end;
  if MinEdge.Z > MaxEdge.Z then
  begin
    SwapValue(MinEdge.Z, MaxEdge.Z);
  end;

end;

procedure TAabbox3DF.Reset;
begin
  MinEdge.X := -1;
  MinEdge.Y := -1;
  MinEdge.Z := -1;

  MaxEdge.X := 1;
  MaxEdge.Y := 1;
  MaxEdge.Z := 1;
end;

procedure TAabbox3DF.Reset(const InitValue: TAabbox3DF);
begin
  Self := InitValue;
end;

procedure TAabbox3DF.Reset(x, y, z: Single);
begin
  MaxEdge.X := x;
  MaxEdge.Y := y;
  MaxEdge.Z := z;

  MaxEdge := MinEdge;
end;

procedure TAabbox3DF.Reset(const InitValue: TVector3DF);
begin
  MaxEdge := InitValue;
  MinEdge := InitValue;
end;



end.